/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Framework.Graphics
{

	public class PostProcessContext : IDisposable
	{
		private ContentManager content;

		public PostProcessContext( ContentManager content )
		{
			this.content = content;
		}

		public void Dispose()
		{
			Uninitialize();
		}

		private bool preferMsaa;
		public bool PreferMultiSampling
		{
			get { return preferMsaa; }
			set { preferMsaa = value; }
		}

		#region Bloom Parameters
		private bool bloomEnable = true;
		public bool BloomEnable
		{
			get { return bloomEnable; }
			set
			{
				bloomEnable = value;
				techniqueUpToDate = false;
			}
		}

		public bool IsBloomEnabled
		{
			get
			{
				UpdateEffectTechnique();
				return effect.CurrentTechnique == effectTechDofAndBloom ||
					effect.CurrentTechnique == effectTechBloom;
			}
		}

		private float bloomThresh = 0.5F;
		public float BloomThresh
		{
			get { return bloomThresh; }
			set
			{
				value = Helpers.Saturate( value );
				if( bloomThresh == value )
					return;

				bloomThresh = value;
				paramsAreUpToDate = false;
			}
		}

		private Vector3 bloomTint = new Vector3( 2.0F );
		public Vector3 BloomTint
		{
			get { return bloomTint; }
			set
			{
				if( bloomTint == value )
					return;

				bloomTint = value;
				paramsAreUpToDate = false;
			}
		}

		private float bloomSaturation = 0.0F;
		public float BloomSaturation
		{
			get { return bloomSaturation; }
			set
			{
				if( bloomSaturation == value )
					return;

				bloomSaturation = value;
				paramsAreUpToDate = false;
			}
		}

		private Vector3 baseTint = new Vector3( 1.0F );
		public Vector3 BaseTint
		{
			get { return baseTint; }
			set
			{
				if( baseTint == value )
					return;

				baseTint = value;
				paramsAreUpToDate = false;
			}
		}

		private float baseSaturation = 1.0F;
		public float BaseSaturation
		{
			get { return baseSaturation; }
			set
			{
				if( baseSaturation == value )
					return;

				baseSaturation = value;
				paramsAreUpToDate = false;
			}
		}
		#endregion

		#region DoF Paramaters
		private bool dofEnable = false;
		public bool DofEnable
		{
			get { return dofEnable; }
			set
			{
				dofEnable = value;
				techniqueUpToDate = false;
			}
		}

		public bool IsDofEnabled
		{
			get
			{
				UpdateEffectTechnique();
				return effect.CurrentTechnique == effectTechDofAndBloom;
			}
		}

		private float dofFocalDistance = 0;
		public float DofFocalDistance
		{
			get { return dofFocalDistance; }
			set
			{
				value = Math.Max( value, 0 );

				if( dofFocalDistance == value )
					return;

				dofFocalDistance = value;
				paramsAreUpToDate = false;
			}
		}

		private float dofNearDistance = 0;
		public float DofNearDistance
		{
			get { return dofNearDistance; }
			set
			{
				value = Math.Max( value, 0 );

				if( dofNearDistance == value )
					return;

				dofNearDistance = value;
				paramsAreUpToDate = false;
			}
		}

		private float dofNearFocus = 0;
		public float DofNearFocus
		{
			get { return dofNearFocus; }
			set
			{
				value = Helpers.Saturate( value );

				if( dofNearFocus == value )
					return;

				dofNearFocus = value;
				paramsAreUpToDate = false;
			}
		}

		private float dofFarDistance = 0;
		public float DofFarDistance
		{
			get { return dofFarDistance; }
			set
			{
				value = Math.Max( value, 0 );

				if( dofFarDistance == value )
					return;

				dofFarDistance = value;
				paramsAreUpToDate = false;
			}
		}

		private float dofFarFocus = 0;
		public float DofFarFocus
		{
			get { return dofFarFocus; }
			set
			{
				value = Helpers.Saturate( value );

				if( dofFarFocus == value )
					return;

				dofFarFocus = value;
				paramsAreUpToDate = false;
			}
		}
		#endregion

		#region Shared Parameters
		private float blurStrength = 8;
		public float BlurStrength
		{
			get { return blurStrength; }
			set
			{
				if( blurStrength == value )
					return;

				blurStrength = value;
				paramsAreUpToDate = false;
			}
		}

		private int blurSteps = 2;
		public int BlurSteps
		{
			get { return blurSteps; }
			set
			{
				if( value < 0 )
					throw new ArgumentOutOfRangeException();

				blurSteps = value;
			}
		}
		#endregion

		#region Surface Format Madness
#if XBOX
		private bool SetupFormats( out DepthFormat depthBufferFormat,
			out SurfaceFormat depthLayerFormat, out SurfaceFormat colorLayerFormat,
			out MultiSampleType msaaType, out int msaaQuality, bool preferMsaa )
		{
			depthBufferFormat = DepthFormat.Depth24Stencil8;
			depthLayerFormat = SurfaceFormat.HalfVector2;
			colorLayerFormat = SurfaceFormat.Bgra1010102;

			if( preferMsaa )
			{
				msaaType = MultiSampleType.TwoSamples;
				msaaQuality = 1;
			}
			else
			{
				msaaType = MultiSampleType.None;
				msaaQuality = 0;
			}
			return true;
		}
#else
		private static readonly MultiSampleType[] msaaNone = new MultiSampleType[]
		{
			MultiSampleType.None,
		};
		private static readonly MultiSampleType[] msaaTypes = new MultiSampleType[]
		{ 
			MultiSampleType.FourSamples,
			MultiSampleType.TwoSamples,
			MultiSampleType.None
		};
		private static readonly SurfaceFormat[] colorLayerFormats = new SurfaceFormat[]
		{
			SurfaceFormat.Bgra1010102,
			SurfaceFormat.Color
		};
		private static readonly SurfaceFormat[] depthLayerFormats = new SurfaceFormat[]
		{
			SurfaceFormat.HalfSingle,
			SurfaceFormat.Single,
			SurfaceFormat.HalfVector2,
		};

		private bool FindFormat( SurfaceFormat[] formats, QueryUsages queryUsage,
			DepthFormat depthBufferFormat, MultiSampleType msaaType, ref int msaaQuality,
			out SurfaceFormat format )
		{
			bool isFullScreen = device.PresentationParameters.IsFullScreen;
			GraphicsDeviceCreationParameters createParams = device.CreationParameters;
			GraphicsAdapter adapter = createParams.Adapter;
			SurfaceFormat adapterFormat = adapter.CurrentDisplayMode.Format;

			format = SurfaceFormat.Unknown;

			foreach( SurfaceFormat checkFormat in formats )
			{
				if( !adapter.CheckDeviceFormat( createParams.DeviceType,
						  adapterFormat, TextureUsage.None, QueryUsages.PostPixelShaderBlending,
						  ResourceType.RenderTarget, checkFormat ) )
					//doesn't support color type
					continue;

				if( !adapter.CheckDepthStencilMatch( createParams.DeviceType,
					adapterFormat, checkFormat, depthBufferFormat ) )
					//can't match depth buffer
					continue;

				int qualityLevels;
				if( !adapter.CheckDeviceMultiSampleType( createParams.DeviceType,
					checkFormat, isFullScreen, msaaType, out qualityLevels ) )
					//can't MSAA with desired setting
					continue;

				format = checkFormat;
				msaaQuality = Math.Min( msaaQuality, qualityLevels );
				return true;
			}

			return false;
		}

		private bool SetupFormats( out DepthFormat depthBufferFormat,
			out SurfaceFormat depthLayerFormat, out SurfaceFormat colorLayerFormat,
			MultiSampleType checkMsaa, out int msaaQuality )
		{
			bool isFullScreen = device.PresentationParameters.IsFullScreen;
			GraphicsDeviceCreationParameters createParams = device.CreationParameters;
			GraphicsAdapter adapter = createParams.Adapter;
			SurfaceFormat adapterFormat = adapter.CurrentDisplayMode.Format;

			depthBufferFormat = DepthFormat.Depth24Stencil8;
			colorLayerFormat = SurfaceFormat.Unknown;
			depthLayerFormat = SurfaceFormat.Unknown;

			if( !adapter.CheckDeviceMultiSampleType( createParams.DeviceType,
				(SurfaceFormat)depthBufferFormat, isFullScreen, checkMsaa,
				out msaaQuality ) )
				//can't match depth buffer
				return false;

			if( !FindFormat( colorLayerFormats, QueryUsages.PostPixelShaderBlending, depthBufferFormat,
				checkMsaa, ref msaaQuality, out colorLayerFormat ) )
				return false;

			if( !FindFormat( depthLayerFormats, QueryUsages.PostPixelShaderBlending, depthBufferFormat,
				checkMsaa, ref msaaQuality, out depthLayerFormat ) )
				return false;

			return true;
		}

		private bool SetupFormats( out DepthFormat depthBufferFormat,
			out SurfaceFormat depthLayerFormat, out SurfaceFormat colorLayerFormat,
			out MultiSampleType msaaType, out int msaaQuality, bool preferMsaa )
		{
			foreach( MultiSampleType checkMsaa in
				preferMsaa ? msaaTypes : msaaNone )
			{
				if( SetupFormats( out depthBufferFormat, out depthLayerFormat,
					out colorLayerFormat, checkMsaa, out msaaQuality ) )
				{
					msaaType = checkMsaa;
					msaaQuality = Math.Max( msaaQuality - 1, 0 );
					return true;
				}
			}

			depthBufferFormat = DepthFormat.Depth24Stencil8;
			colorLayerFormat = SurfaceFormat.Color;
			depthLayerFormat = SurfaceFormat.HalfVector2;
			msaaType = MultiSampleType.None;
			msaaQuality = 0;

			return false;
		}
#endif
		#endregion

		#region Full-Screen Quad (well, triangle)
		private struct ScreenFillVert
		{
			public float x, y;
			public float u, v;

			public ScreenFillVert( float x, float y, float u, float v )
			{
				this.x = x;
				this.y = y;
				this.u = u;
				this.v = v;
			}

			public static readonly VertexElement[] VertexElements = new VertexElement[]
				{
					new VertexElement( 0, 0, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
					new VertexElement( 0, 8, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ),
				};

			public static readonly int Stride = 16;
		}

		private ScreenFillVert[] fullScreenFill;
		private ScreenFillVert[] quarterScreenFill;

		private ScreenFillVert[] GenScreenFill( int width, int height )
		{
			float uAdj = -1.0F / width;
			float vAdj = 1.0F / height;

			return new ScreenFillVert[]
			{
				new ScreenFillVert( -1 + uAdj, 1 + vAdj, 0, 0 ),
				new ScreenFillVert( 3 + uAdj, 1 + vAdj, 2, 0 ),
				new ScreenFillVert( -1 + uAdj, -3 + vAdj, 0, 2 )
			};
		}

		private void InitScreenFill()
		{
			screenFillDecl = new VertexDeclaration( device, ScreenFillVert.VertexElements );

			fullScreenFill = GenScreenFill( screenWidth, screenHeight );
			quarterScreenFill = GenScreenFill( screenWidth / 4, screenHeight / 4 );
		}
		#endregion

		private bool paramsAreUpToDate, techniqueUpToDate;

		private Effect effect;

		private EffectTechnique
			effectTechDofAndBloom,
			effectTechBloom,
			effectTechPassthrough;

		private EffectPass downscalePass;
		private EffectPass blurPass;
		private EffectPass combinePass;

		private EffectParameter effectFullPixelSize, effectBlurPixelSize;
		private EffectParameter effectGaussianTaps, effectPoissonTaps;

		private Vector4[] gaussianTaps, poissonTaps;

		private void InitEffect()
		{
			effect = content.Load<Effect>( "PostProcess" );

			effectTechDofAndBloom = effect.Techniques["DofAndBloom"];
			if( !effectTechDofAndBloom.Validate() )
				effectTechDofAndBloom = null;

			effectTechBloom = effect.Techniques["Bloom"];
			if( !effectTechBloom.Validate() )
				effectTechBloom = null;

			effectTechPassthrough = effect.Techniques["Passthrough"];
			if( !effectTechPassthrough.Validate() )
				throw new CobaltException( "Even minimal post-processing is not supported on this hardware." );

			effectFullPixelSize = effect.Parameters["FullPixelSize"];
			effectBlurPixelSize = effect.Parameters["BlurPixelSize"];
			effectGaussianTaps = effect.Parameters["GaussianTaps"];
			effectPoissonTaps = effect.Parameters["PoissonTaps"];

			InitGaussWeights();
		}

		#region InitGaussWeights
		private void InitGaussWeights()
		{
			int gaussianSize = effectGaussianTaps.RowCount;

			if( gaussianTaps == null || gaussianTaps.Length != gaussianSize )
				gaussianTaps = new Vector4[gaussianSize];

			gaussianTaps[0].W = GetGaussianWeight( 0 );
			for( int i = 0; i < gaussianTaps.Length; i++ )
				gaussianTaps[i].Z = GetGaussianWeight( i + 1 );

			float sum = gaussianTaps[0].W;

			for( int i = 0; i < gaussianTaps.Length; i++ )
				sum += gaussianTaps[i].Z * 2;

			float inv = 1.0F / sum;

			gaussianTaps[0].W *= inv;
			for( int i = 0; i < gaussianTaps.Length; i++ )
				gaussianTaps[i].Z *= inv;

			//xys and the actual set-to-effect later by UpdateGaussianTaps
		}

		private float GetGaussianWeight( float n )
		{
			return (float)((1.0 / Math.Sqrt( 2 * Math.PI * blurStrength )) *
						   Math.Exp( -(n * n) / (2 * blurStrength * blurStrength) ));
		}
		#endregion

		private GraphicsDevice device;
		private int screenWidth, screenHeight;

		private DepthStencilBuffer depthBuffer;
		private RenderTarget2D depthLayer;
		private RenderTarget2D colorLayer;

		private int pingPongIdx;
		private RenderTarget2D[] pingPongChain;

		private VertexDeclaration screenFillDecl;

		private void DoInitialize()
		{
			int msaaQuality;
			MultiSampleType msaaType;
			DepthFormat depthBufferFormat;
			SurfaceFormat depthLayerFormat, colorLayerFormat;

			if( !SetupFormats( out depthBufferFormat, out depthLayerFormat,
				out colorLayerFormat, out msaaType, out msaaQuality, preferMsaa ) )
				throw new CobaltException( "Unable to set up device formats." );

			this.depthBuffer = new DepthStencilBuffer( device, screenWidth, screenHeight,
				depthBufferFormat, msaaType, msaaQuality );
			this.colorLayer = new RenderTarget2D( device, screenWidth, screenHeight, 1,
				colorLayerFormat, msaaType, msaaQuality, RenderTargetUsage.PlatformContents );
			this.depthLayer = new RenderTarget2D( device, screenWidth, screenHeight, 1,
				depthLayerFormat, msaaType, msaaQuality, RenderTargetUsage.PlatformContents );

			pingPongChain = new RenderTarget2D[2];
			for( int i = 0; i < pingPongChain.Length; i++ )
				pingPongChain[i] = new RenderTarget2D( device, screenWidth / 4, screenHeight / 4, 1, SurfaceFormat.Color );
			pingPongIdx = 0;

			InitScreenFill();
			InitEffect();
		}

		public void Initialize( GraphicsDevice device, int? width, int? height )
		{
			Uninitialize();

			PresentationParameters presentParams = device.PresentationParameters;

			this.device = device;
			this.screenWidth = width ?? presentParams.BackBufferWidth;
			this.screenHeight = height ?? presentParams.BackBufferHeight;

			try
			{
				DoInitialize();
			}
			catch
			{
				Uninitialize();
				throw;
			}
		}

		public void Uninitialize()
		{
			if( pingPongChain != null )
			{
				foreach( RenderTarget2D target in pingPongChain )
					target.Dispose();
				pingPongChain = null;
			}

			Helpers.DisposeAndNull( ref colorLayer );
			Helpers.DisposeAndNull( ref depthLayer );
			Helpers.DisposeAndNull( ref depthBuffer );

			Helpers.DisposeAndNull( ref screenFillDecl );

			device = null;
		}

		private void RenderSurfaces( DrawList surfaces, ShadingContext shadingCtx )
		{
			device.DepthStencilBuffer = depthBuffer;
			device.RenderState.DepthBufferEnable = true;

			device.SetRenderTarget( 0, depthLayer );
			device.Clear( ClearOptions.DepthBuffer | ClearOptions.Stencil | ClearOptions.Target,
				new Vector4( 1, 1, 1, 1 ), 1.0F, 0 );
			surfaces.Render( shadingCtx, Material.Layer.Depth );

			device.SetRenderTarget( 0, colorLayer );
			device.Clear( ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1.0F, 0 );
			surfaces.Render( shadingCtx, Material.Layer.Light );

			device.DepthStencilBuffer = null;
		}

		/// <summary>
		/// The last ping-pong target that was written to.
		/// </summary>
		private RenderTarget2D PingPongSource
		{
			get
			{
				int idx = pingPongIdx - 1;
				if( idx == -1 )
					idx = pingPongChain.Length - 1;
				return pingPongChain[idx];
			}
		}

		/// <summary>
		/// The next ping-pong target to be written to.
		/// </summary>
		private RenderTarget2D PingPongTarget
		{
			get { return pingPongChain[pingPongIdx]; }
		}

		private void FlipPingPongChain()
		{
			pingPongIdx = (pingPongIdx + 1) % pingPongChain.Length;
		}

		private void PostProcess( ShadingContext shadingCtx, RenderTarget2D drawTarget )
		{
			//set up basic rendering parameters

			UpdateEffectTechnique();
			UpdateEffectParameters( shadingCtx );

			device.VertexDeclaration = screenFillDecl;

			float dxFull = 1.0F / screenWidth;
			float dyFull = 1.0F / screenHeight;
			float dxBlur = 1.0F / (screenWidth / 4);
			float dyBlur = 1.0F / (screenHeight / 4);

			effectFullPixelSize.SetValue( new Vector2( dxFull, dyFull ) );
			effectBlurPixelSize.SetValue( new Vector2( dxBlur, dyBlur ) );

			for( int i = 0; i < 3; i++ )
			{
				device.SamplerStates[i].MinFilter = TextureFilter.Linear;
				device.SamplerStates[i].MagFilter = TextureFilter.Linear;
				device.SamplerStates[i].MipFilter = TextureFilter.None;
				device.SamplerStates[i].AddressU = TextureAddressMode.Clamp;
				device.SamplerStates[i].AddressV = TextureAddressMode.Clamp;
			}

			//gogo

			effect.Begin();

			//downscale

			if( downscalePass != null )
			{
				device.SetRenderTarget( 0, PingPongTarget );
				device.Textures[0] = colorLayer.GetTexture();

				downscalePass.Begin();
				device.DrawUserPrimitives( PrimitiveType.TriangleList, quarterScreenFill, 0, 1 );
				shadingCtx.PerformanceCounters.UpdateGeometryCounters( 3, 1, 1 );
				downscalePass.End();

				FlipPingPongChain();

				//blur

				if( blurPass != null && blurStrength > 0 )
				{
					for( int i = 0; i < blurSteps; i++ )
					{
						UpdateGaussianTaps( 0, dyBlur );
						device.SetRenderTarget( 0, PingPongTarget );
						device.Textures[1] = PingPongSource.GetTexture();

						blurPass.Begin();
						device.DrawUserPrimitives( PrimitiveType.TriangleList, quarterScreenFill, 0, 1 );
						shadingCtx.PerformanceCounters.UpdateGeometryCounters( 3, 1, 1 );
						blurPass.End();

						FlipPingPongChain();

						UpdateGaussianTaps( dxBlur, 0 );
						device.SetRenderTarget( 0, PingPongTarget );
						device.Textures[1] = PingPongSource.GetTexture();

						blurPass.Begin();
						device.DrawUserPrimitives( PrimitiveType.TriangleList, quarterScreenFill, 0, 1 );
						shadingCtx.PerformanceCounters.UpdateGeometryCounters( 3, 1, 1 );
						blurPass.End();

						FlipPingPongChain();
					}
				}
			}

			//composit

			UpdatePoissonTaps( dxFull, dyFull, dxBlur, dyBlur );

			device.SetRenderTarget( 0, drawTarget );
			device.Textures[0] = colorLayer.GetTexture();
			device.Textures[1] = downscalePass != null ? PingPongSource.GetTexture() : null;
			device.Textures[2] = depthLayer.GetTexture();

			combinePass.Begin();
			device.DrawUserPrimitives( PrimitiveType.TriangleList, fullScreenFill, 0, 1 );
			shadingCtx.PerformanceCounters.UpdateGeometryCounters( 3, 1, 1 );
			combinePass.End();

			effect.End();

			device.Textures[0] = null;
			device.Textures[1] = null;
			device.Textures[2] = null;
		}

		private void UpdateEffectTechnique()
		{
			if( effect == null || techniqueUpToDate )
				return;

			EffectTechnique technique;
			if( dofEnable && effectTechDofAndBloom != null )
			{
				technique = effectTechDofAndBloom;
			}
			else if( bloomEnable && effectTechBloom != null )
			{
				technique = effectTechBloom;
			}
			else
			{
				technique = effectTechPassthrough;
			}

			effect.CurrentTechnique = technique;
			downscalePass = technique.Passes["Downscale"];
			blurPass = technique.Passes["Blur"];
			combinePass = technique.Passes["Combine"];

			techniqueUpToDate = true;
			paramsAreUpToDate = false;
		}

		private void UpdateEffectParameters( ShadingContext ctx )
		{
			if( effect == null || paramsAreUpToDate )
				return;

			if( bloomEnable )
			{
				effect.Parameters["BloomThresh"].SetValue( bloomThresh );
				effect.Parameters["BloomTint"].SetValue( bloomTint );
				effect.Parameters["BloomSaturation"].SetValue( bloomSaturation );
				effect.Parameters["BaseTint"].SetValue( baseTint );
				effect.Parameters["BaseSaturation"].SetValue( baseSaturation );
			}
			else
			{
				effect.Parameters["BloomTint"].SetValue( Vector3.Zero );
				effect.Parameters["BaseTint"].SetValue( Vector3.One );
				effect.Parameters["BaseSaturation"].SetValue( 1.0F );
			}

			Vector2 depthRange = ctx.DepthRange;
			float invDepthSpan = 1.0F / (depthRange.Y - depthRange.X);

			effect.Parameters["DofRange"].SetValue( new Vector3(
					(dofNearDistance - depthRange.X) * invDepthSpan,
					(dofFocalDistance - depthRange.X) * invDepthSpan,
					(dofFarDistance - depthRange.X) * invDepthSpan ) );
			effect.Parameters["DofDefocus"].SetValue(
				dofEnable ? new Vector2( 1 - dofNearFocus, 1 - dofFarFocus ) :
				Vector2.Zero );

			paramsAreUpToDate = true;
		}

		private void UpdateGaussianTaps( float dx, float dy )
		{
			for( int i = 0; i < gaussianTaps.Length; i++ )
			{
				float offset = i * 2 + 1.5F;
				gaussianTaps[i].X = dx * offset;
				gaussianTaps[i].Y = dy * offset;
			}

			effectGaussianTaps.SetValue( gaussianTaps );
		}

		#region UpdatePoissonTaps
		private void UpdatePoissonTaps( float dxFull, float dyFull, float dxBlur, float dyBlur )
		{
			int poissonSize = effectPoissonTaps.RowCount;

			if( poissonSize > basePoissonTaps.Length )
				throw new CobaltException( "PostProcess effect/driver interface mismatch." );

			if( poissonTaps == null || poissonTaps.Length != poissonSize )
				poissonTaps = new Vector4[poissonSize];

			for( int i = 0; i < poissonTaps.Length; i++ )
			{
				Vector2 srcVec = basePoissonTaps[i];
				poissonTaps[i] = new Vector4(
					srcVec.X * dxFull,
					srcVec.Y * dyFull,
					srcVec.X * dxBlur,
					srcVec.Y * dyBlur );
			}
		}

		private static readonly Vector2[] basePoissonTaps = new Vector2[]
		{
			//new Vector2( 0.0F, 0.0F ), << implied
			new Vector2( 0.527837F, -0.085868F ),
			new Vector2( -0.040088F, 0.536087F ),
			new Vector2( -0.670445F, -0.179949F ),
			new Vector2( -0.419418F, -0.616039F ),
			new Vector2( 0.440453F, -0.639399F ),
			new Vector2( -0.757088F, 0.649334F ),
			new Vector2( 0.574619F, 0.685879F ),
		};
		#endregion

		public void Render( DrawList surfaces, ShadingContext shadingCtx, RenderTarget2D drawTarget )
		{
			RenderSurfaces( surfaces, shadingCtx );
			PostProcess( shadingCtx, drawTarget );
		}

	}

	public class PostProcess : GameComponent
	{
		private ContentManager content;
		private PostProcessContext context;

		public PostProcess( Game game )
			: base( game )
		{
			content = new ContentManager( Game.Services, "Content\\Cobalt.Framework" );
			context = new PostProcessContext( content );
		}

		private int? screenWidth;
		public int? ScreenWidth
		{
			get { return screenWidth; }
			set
			{
				if( value != null && value.Value < 1 )
					throw new ArgumentOutOfRangeException();
				screenWidth = value;
			}
		}

		private int? screenHeight;
		public int? ScreenHeight
		{
			get { return screenHeight; }
			set
			{
				if( value != null && value.Value < 1 )
					throw new ArgumentOutOfRangeException();
				screenHeight = value;
			}
		}

		public PostProcessContext Context
		{
			get { return context; }
		}

		void graphicsService_DeviceDisposing( object sender, EventArgs e )
		{
			context.Uninitialize();
		}

		void graphicsService_DeviceCreated( object sender, EventArgs e )
		{
			context.Initialize( graphicsService.GraphicsDevice, screenWidth, screenHeight );
		}

		private IGraphicsDeviceService graphicsService;
		
		public override void Initialize()
		{
			base.Initialize();

			graphicsService = Game.Services.GetService( typeof( IGraphicsDeviceService ) )
				as IGraphicsDeviceService;
			if( graphicsService == null )
				throw new InvalidOperationException();

			graphicsService.DeviceCreated += new EventHandler( graphicsService_DeviceCreated );
			graphicsService.DeviceDisposing += new EventHandler( graphicsService_DeviceDisposing );

			if( graphicsService.GraphicsDevice != null )
			{
				context.Initialize( graphicsService.GraphicsDevice, screenWidth, screenHeight );
			}
		}

		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				context.Uninitialize();
			}

			base.Dispose( disposing );
		}

		public void Render( DrawList surfaces, ShadingContext shadingCtx, RenderTarget2D drawTarget )
		{
			context.Render( surfaces, shadingCtx, drawTarget );
		}
	}

}